'use strict';

/*
 * yield_geo_near_dedup.js (extends yield_geo_near.js)
 *
 * Intersperse geo $near queries with updates of non-geo fields to test deduplication.
 */
load('jstests/concurrency/fsm_libs/extend_workload.js');      // for extendWorkload
load('jstests/concurrency/fsm_workloads/yield_geo_near.js');  // for $config

var $config = extendWorkload(
    $config,
    function($config, $super) {

        $config.states.remove = function remove(db, collName) {
            var id = Random.randInt(this.nDocs);
            var doc = db[collName].findOne({_id: id});
            if (doc !== null) {
                var res = db[collName].remove({_id: id});
                assertAlways.writeOK(res);
                if (res.nRemoved > 0) {
                    // Re-insert the document with the same '_id', but an incremented
                    // 'timesInserted' to
                    // distinguish it from the deleted document.
                    doc.timesInserted++;
                    assertAlways.writeOK(db[collName].insert(doc));
                }
            }
        };

        /*
         * Use geo $nearSphere query to find points near the origin. Note this should be done using
         *the
         * geoNear command, rather than a $nearSphere query, as the $nearSphere query doesn't work
         *in a
         * sharded environment. Unfortunately this means we cannot batch the request.
         *
         * Only points are covered in this test as there is no guarantee that geometries indexed in
         * multiple cells will be deduplicated correctly with interspersed updates. If multiple
         *index
         * cells for the same geometry occur in the same search interval, an update may cause
         *geoNear
         * to return the same document multiple times.
         */
        $config.states.query = function geoNear(db, collName) {
            // This distance gets about 80 docs around the origin. There is one doc inserted
            // every 1m^2 and the area scanned by a 5m radius is PI*(5m)^2 ~ 79.
            var maxDistance = 5;

            var res = db.runCommand(
                {geoNear: collName, near: [0, 0], maxDistance: maxDistance, spherical: true});
            assertWhenOwnColl.commandWorked(res);
            assertWhenOwnColl(function verifyResults() {
                var results = res.results;
                var seenObjs = [];
                for (var i = 0; i < results.length; i++) {
                    var doc = results[i].obj;

                    // The pair (_id, timesInserted) is the smallest set of attributes that uniquely
                    // identifies a document.
                    var objToSearchFor = {
                        _id: doc._id,
                        timesInserted: doc.timesInserted
                    };
                    var found = seenObjs.some(function(obj) {
                        return bsonWoCompare(obj, objToSearchFor) === 0;
                    });
                    assertWhenOwnColl(!found,
                                      'geoNear command returned the document ' + tojson(doc) +
                                          ' multiple times: ' + tojson(seenObjs));
                    seenObjs.push(objToSearchFor);
                }
            });
        };

        $config.data.genUpdateDoc = function genUpdateDoc() {
            // Attempts to perform an in-place update to trigger an invalidation on MMAP v1.
            return {
                $inc: {timesUpdated: 1}
            };
        };

        $config.data.getIndexSpec = function getIndexSpec() {
            return {
                geo: '2dsphere'
            };
        };

        $config.data.getReplaceSpec = function getReplaceSpec(i, coords) {
            return {
                _id: i,
                geo: coords,
                timesUpdated: 0,
                timesInserted: 0
            };
        };

        return $config;
    });
